package com.xt.base.dao.hibernate.userType;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

import com.xt.base.util.json.Jsoner;

/**
 * <p>
 * Abstract superclass for type-safe enums with integer values suitable for use
 * in <code>switch</code> statements.
 * </p>
 * 
 * <p>
 * <em>NOTE:</em>Due to the way in which Java ClassLoaders work, comparing
 * <code>Enum</code> objects should always be done using the equals() method,
 * not <code>==</code>. The equals() method will try <code>==</code> first
 * so in most cases the effect is the same.
 * </p>
 * 
 * <p>
 * To use this class, it must be subclassed. For example:
 * </p>
 * 
 * <pre>
 * public final class JavaVersionEnum extends ValuedEnum {
 * 	//standard enums for version of JVM
 * 	public static final int JAVA1_0_VALUE = 100;
 * 
 * 	public static final int JAVA1_1_VALUE = 110;
 * 
 * 	public static final int JAVA1_2_VALUE = 120;
 * 
 * 	public static final int JAVA1_3_VALUE = 130;
 * 
 * 	public static final JavaVersionEnum JAVA1_0 = new JavaVersionEnum(
 * 			&quot;Java 1.0&quot;, JAVA1_0_VALUE);
 * 
 * 	public static final JavaVersionEnum JAVA1_1 = new JavaVersionEnum(
 * 			&quot;Java 1.1&quot;, JAVA1_1_VALUE);
 * 
 * 	public static final JavaVersionEnum JAVA1_2 = new JavaVersionEnum(
 * 			&quot;Java 1.2&quot;, JAVA1_2_VALUE);
 * 
 * 	public static final JavaVersionEnum JAVA1_3 = new JavaVersionEnum(
 * 			&quot;Java 1.3&quot;, JAVA1_3_VALUE);
 * 
 * 	private JavaVersionEnum(String name, int value) {
 * 		super(name, value);
 * 	}
 * 
 * 	public static JavaVersionEnum getEnum(String javaVersion) {
 * 		return (JavaVersionEnum) getEnum(JavaVersionEnum.class, javaVersion);
 * 	}
 * 
 * 	public static JavaVersionEnum getEnum(int javaVersion) {
 * 		return (JavaVersionEnum) getEnum(JavaVersionEnum.class, javaVersion);
 * 	}
 * 
 * 	public static Map getEnumMap() {
 * 		return getEnumMap(JavaVersionEnum.class);
 * 	}
 * 
 * 	public static List getEnumList() {
 * 		return getEnumList(JavaVersionEnum.class);
 * 	}
 * 
 * 	public static Iterator iterator() {
 * 		return iterator(JavaVersionEnum.class);
 * 	}
 * }
 * </pre>
 * 
 * <p>
 * <em>NOTE:</em>These are declared <code>final</code>, so compilers may
 * inline the code. Ensure you recompile everything when using final.
 * </p>
 * 
 * <p>
 * The above class could then be used as follows:
 * </p>
 * 
 * <pre>
 * public void doSomething(JavaVersion ver) {
 * 	switch (ver.getValue()) {
 * 	case JAVA1_0_VALUE:
 * 		// ...
 * 		break;
 * 	case JAVA1_1_VALUE:
 * 		// ...
 * 		break;
 * 	//...
 * 	}
 * }
 * </pre>
 * 
 * <p>
 * As shown, each enum has a name and a value. These can be accessed using
 * <code>getName</code> and <code>getValue</code>.
 * </p>
 * 
 * <p>
 * The <code>getEnum</code> and <code>iterator</code> methods are
 * recommended. Unfortunately, Java restrictions require these to be coded as
 * shown in each subclass. An alternative choice is to use the {@link EnumUtils}
 * class.
 * </p>
 * 
 * @author 杨洪波
 * @version $Revision: 1407 $<br>
 *          $Id: Enum.java 
 */
@SuppressWarnings("unchecked")
public abstract class Enum extends Jsoner implements Comparable, Serializable, UserType {

	/** Lang version 1.0.1 serial compatibility */
	private static final long serialVersionUID = -487045951170455942L;

	// After discussion, the default size for HashMaps is used, as the
	// sizing algorithm changes across the JDK versions
	/**
	 * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
	 */
	private static final Map EMPTY_MAP = Collections
			.unmodifiableMap(new HashMap(0));

	/**
	 * <code>Map</code>, key of class name, value of <code>Entry</code>.
	 */
	private static final Map cEnumClasses = new HashMap();

	/**
	 * The string representation of the Enum.
	 */
	private final String iName;

	/**
	 * The hashcode representation of the Enum.
	 */
	private transient final int iHashCode;

	/**
	 * The value contained in enum.
	 */
	private final int iValue;

	/**
	 * The value contained in enum.
	 */
	private final int iCharValue;

	/**
	 * The value contained in enum.
	 */
	private final int iNumValue;

	/**
	 * The toString representation of the Enum.
	 * 
	 * @since 2.0
	 */
	protected transient String iToString = null;

	private final String iDescription;

	private final String iCode;

	/**
	 * <p>
	 * Enable the iterator to retain the source code order.
	 * </p>
	 */
	private static class Entry {
		/**
		 * Map of Enum name to Enum.
		 */
		final Map map = new HashMap();

		/**
		 * Map of Enum name to Enum.
		 */
		final Map unmodifiableMap = Collections.unmodifiableMap(map);

		/**
		 * List of Enums in source code order.
		 */
		final List list = new ArrayList(25);

		/**
		 * Map of Enum name to Enum.
		 */
		final List unmodifiableList = Collections.unmodifiableList(list);

		/**
		 * <p>
		 * Restrictive constructor.
		 * </p>
		 */
		private Entry() {
		}
	}

	public Enum(String name, int value, String code, String description) {
		super();
		init(name);
		iName = name;
		iValue = value;
		iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
		iDescription = description;
		iCode = code;
		iCharValue = 0;
		iNumValue = 0;
	}

	public Enum(String name, int value, String description) {
		super();
		init(name);
		iName = name;
		iValue = value;
		iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
		iDescription = description;
		iCode = null;
		iCharValue = 0;
		iNumValue = 0;
	}

	public Enum() {
		iName = null;
		iValue = 0;
		iHashCode = 0;
		iDescription = null;
		iCode = null;
		iCharValue = 0;
		iNumValue = 0;
	}

	/**
	 * <p>
	 * Constructor to add a new named item to the enumeration.
	 * </p>
	 * 
	 * @param name
	 *            the name of the enum object, must not be empty or
	 *            <code>null</code>
	 * @param value
	 *            the value of enum item
	 * @throws IllegalArgumentException
	 *             if the name is <code>null</code> or an empty string
	 * @throws IllegalArgumentException
	 *             if the getEnumClass() method returns a null or invalid Class
	 */
	protected Enum(String name, int value) {
		super();
		init(name);
		iName = name;
		iValue = value;
		iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
		iCharValue = 0;
		iNumValue = 0;
		iDescription = null;
		iCode = null;
		// cannot create toString here as subclasses may want to include other
		// data
	}

	public Enum(String name, int value, int charValue, int numValue) {

		super();
		init(name);
		iName = name;
		iValue = value;
		iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
		iCharValue = charValue;
		iNumValue = numValue;
		iDescription = null;
		iCode = null;
	}

	/**
	 * Initializes the enumeration.
	 * 
	 * @param name
	 *            the enum name
	 * @throws IllegalArgumentException
	 *             if the name is null or empty or duplicate
	 * @throws IllegalArgumentException
	 *             if the enumClass is null or invalid
	 */
	private void init(String name) {
		if (StringUtils.isEmpty(name)) {
			throw new IllegalArgumentException(
					"The Enum name must not be empty or null");
		}

		Class enumClass = getEnumClass();
		if (enumClass == null) {
			throw new IllegalArgumentException(
					"getEnumClass() must not be null");
		}
		Class cls = getClass();
		boolean ok = false;
		// ���Ҵ���ĸ����ǲ���enumClass
		while (cls != null && cls != Enum.class) {
			if (cls == enumClass) {
				ok = true;
				break;
			}
			cls = cls.getSuperclass();
		}
		if (ok == false) {
			throw new IllegalArgumentException(
					"getEnumClass() must return a superclass of this class");
		}

		// create entry
		Entry entry = (Entry) cEnumClasses.get(enumClass);
		if (entry == null) {
			entry = createEntry(enumClass);
			cEnumClasses.put(enumClass, entry);
		}
		if (entry.map.containsKey(name)) {
			throw new IllegalArgumentException(
					"The Enum name must be unique, '" + name
							+ "' has already been added");
		}
		entry.map.put(name, this);
		entry.list.add(this);
	}

	/**
	 * <p>
	 * Handle the deserialization of the class to ensure that multiple copies
	 * are not wastefully created, or illegal enum types created.
	 * </p>
	 * 
	 * @return the resolved object
	 */
	protected Object readResolve() {
		Entry entry = (Entry) cEnumClasses.get(getEnumClass());
		if (entry == null) {
			return null;
		}
		return (Enum) entry.map.get(getName());
	}

	// --------------------------------------------------------------------------------

	/**
	 * <p>
	 * Gets an <code>Enum</code> object by class and name.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the Enum to get, must not be <code>null</code>
	 * @param name
	 *            the name of the <code>Enum</code> to get, may be
	 *            <code>null</code>
	 * @return the enum object, or <code>null</code> if the enum does not
	 *         exist
	 * @throws IllegalArgumentException
	 *             if the enum class is <code>null</code>
	 */
	static public <T extends Enum>  T getEnum(Class<T> enumClass, String name) {
		Entry entry = getEntry(enumClass);
		if (entry == null) {
			return null;
		}
		return (T) entry.map.get(name);
	}

	/**
	 * <p>
	 * Gets an <code>Enum</code> object by class and value.
	 * </p>
	 * 
	 * <p>
	 * This method loops through the list of <code>Enum</code>, thus if there
	 * are many <code>Enum</code>s this will be slow.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get
	 * @param value
	 *            the value of the <code>Enum</code> to get
	 * @return the enum object, or null if the enum does not exist
	 * @throws IllegalArgumentException
	 *             if the enum class is <code>null</code>
	 */
	public static Enum getEnum(Class<? extends Enum> enumClass, int value) {
		if (enumClass == null) {
			throw new IllegalArgumentException(
					"The Enum Class must not be null");
		}
		List list = Enum.getEnumList(enumClass);
		for (Iterator it = list.iterator(); it.hasNext();) {
			Enum enumeration = (Enum) it.next();
			if (enumeration.getValue() == value) {
				return enumeration;
			}
		}
		return null;
	}
	
	static public<T extends Enum>  T get(Class<T> clazz,int value){
		return (T)getEnum(clazz, value);
	}
	
	static public<T extends Enum>  T get(Class<T> clazz,String name){
		return (T)getEnum(clazz, name);
	}

	/**
	 * <p>
	 * Get value of enum item.
	 * </p>
	 * 
	 * @return the enum item's value.
	 */
	public final int getValue() {
		return iValue;
	}

	public final int getCharValue() {
		return iCharValue;
	}

	public final int getNumValue() {
		return iNumValue;
	}

	/**
	 * <p>
	 * Gets the <code>Map</code> of <code>Enum</code> objects by name using
	 * the <code>Enum</code> class.
	 * </p>
	 * 
	 * <p>
	 * If the requested class has no enum objects an empty <code>Map</code> is
	 * returned.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get, must not be
	 *            <code>null</code>
	 * @return the enum object Map
	 * @throws IllegalArgumentException
	 *             if the enum class is <code>null</code>
	 * @throws IllegalArgumentException
	 *             if the enum class is not a subclass of Enum
	 */
	protected static Map getEnumMap(Class<? extends Enum> enumClass) {
		Entry entry = getEntry(enumClass);
		if (entry == null) {
			return EMPTY_MAP;
		}
		return entry.unmodifiableMap;
	}

	/**
	 * <p>
	 * Gets the <code>List</code> of <code>Enum</code> objects using the
	 * <code>Enum</code> class.
	 * </p>
	 * 
	 * <p>
	 * The list is in the order that the objects were created (source code
	 * order). If the requested class has no enum objects an empty
	 * <code>List</code> is returned.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get, must not be
	 *            <code>null</code>
	 * @return the enum object Map
	 * @throws IllegalArgumentException
	 *             if the enum class is <code>null</code>
	 * @throws IllegalArgumentException
	 *             if the enum class is not a subclass of Enum
	 */
	static protected<T extends Enum>  List<T> getEnumList(Class<T> enumClass) {
		Entry entry = getEntry(enumClass);
		if (entry == null) {
			try {
				Class.forName(enumClass.getName());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			entry = getEntry(enumClass);
		}
		if (entry == null) {
			return Collections.EMPTY_LIST;
		}
		return entry.unmodifiableList;
	}

	/**
	 * <p>
	 * Gets an <code>Iterator</code> over the <code>Enum</code> objects in
	 * an <code>Enum</code> class.
	 * </p>
	 * 
	 * <p>
	 * The <code>Iterator</code> is in the order that the objects were created
	 * (source code order). If the requested class has no enum objects an empty
	 * <code>Iterator</code> is returned.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get, must not be
	 *            <code>null</code>
	 * @return an iterator of the Enum objects
	 * @throws IllegalArgumentException
	 *             if the enum class is <code>null</code>
	 * @throws IllegalArgumentException
	 *             if the enum class is not a subclass of Enum
	 */
	public static Iterator iterator(Class enumClass) {
		return Enum.getEnumList(enumClass).iterator();
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets an <code>Entry</code> from the map of Enums.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get
	 * @return the enum entry
	 */
	private static Entry getEntry(Class<? extends Enum> enumClass) {
		if (enumClass == null) {
			throw new IllegalArgumentException(
					"The Enum Class must not be null");
		}
		if (Enum.class.isAssignableFrom(enumClass) == false) {
			throw new IllegalArgumentException(
					"The Class must be a subclass of Enum");
		}
		Entry entry = (Entry) cEnumClasses.get(enumClass);
		return entry;
	}

	/**
	 * <p>
	 * Creates an <code>Entry</code> for storing the Enums.
	 * </p>
	 * 
	 * <p>
	 * This accounts for subclassed Enums.
	 * </p>
	 * 
	 * @param enumClass
	 *            the class of the <code>Enum</code> to get
	 * @return the enum entry
	 */
	private static Entry createEntry(Class enumClass) {
		Entry entry = new Entry();
		Class cls = enumClass.getSuperclass();
		while (cls != null && cls != Enum.class) {
			Entry loopEntry = (Entry) cEnumClasses.get(cls);
			if (loopEntry != null) {
				entry.list.addAll(loopEntry.list);
				entry.map.putAll(loopEntry.map);
				break; // stop here, as this will already have had superclasses
				// added
			}
			cls = cls.getSuperclass();
		}
		return entry;
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Retrieve the name of this Enum item, set in the constructor.
	 * </p>
	 * 
	 * @return the <code>String</code> name of this Enum item
	 */
	public final String getName() {
		return iName;
	}

	/**
	 * <p>
	 * Retrieves the Class of this Enum item, set in the constructor.
	 * </p>
	 * 
	 * <p>
	 * This is normally the same as <code>getClass()</code>, but for advanced
	 * Enums may be different. If overridden, it must return a constant value.
	 * </p>
	 * 
	 * @return the <code>Class</code> of the enum
	 * @since 2.0
	 */
	public Class getEnumClass() {
		return getClass();
	}

	/**
	 * add by zhouping
	 * 
	 * @return
	 */
	public final String getDescription() {
		return iDescription;
	}

	/**
	 * add by zhouping
	 * 
	 * @return
	 */
	public final String getCode() {
		return iCode;
	}

	/**
	 * <p>
	 * Tests for equality.
	 * </p>
	 * 
	 * <p>
	 * Two Enum objects are considered equal if they have the same class names
	 * and the same names. Identity is tested for first, so this method usually
	 * runs fast.
	 * </p>
	 * 
	 * <p>
	 * If the parameter is in a different class loader than this instance,
	 * reflection is used to compare the names.
	 * </p>
	 * 
	 * @param other
	 *            the other object to compare for equality
	 * @return <code>true</code> if the Enums are equal
	 */
	public final boolean equals(Object other) {
		if (other == this) {
			return true;
		} else if (other == null) {
			return false;
		} else if (other.getClass() == this.getClass()) {
			// Ok to do a class cast to Enum here since the test above
			// guarantee both
			// classes are in the same class loader.
			return iName.equals(((Enum) other).iName);
		} else {
			// This and other are in different class loaders, we must check
			// indirectly
			if (other.getClass().getName().equals(this.getClass().getName()) == false) {
				return false;
			}
			try {
				Method mth = other.getClass().getMethod("getName", null);
				String name = (String) mth.invoke(other, null);
				return iName.equals(name);
			} catch (NoSuchMethodException e) {
				// ignore - should never happen
			} catch (IllegalAccessException e) {
				// ignore - should never happen
			} catch (InvocationTargetException e) {
				// ignore - should never happen
			}
			return false;
		}
	}

	/**
	 * <p>
	 * Returns a suitable hashCode for the enumeration.
	 * </p>
	 * 
	 * @return a hashcode based on the name
	 */
	public final int hashCode() {
		return iHashCode;
	}

	/**
	 * <p>
	 * Tests for order.
	 * </p>
	 * 
	 * <p>
	 * The default ordering is numeric by value, but this can be overridden by
	 * subclasses.
	 * </p>
	 * 
	 * @see java.lang.Comparable#compareTo(Object)
	 * @param other
	 *            the other object to compare to
	 * @return -ve if this is less than the other object, +ve if greater than,
	 *         <code>0</code> of equal
	 * @throws ClassCastException
	 *             if other is not an <code>Enum</code>
	 * @throws NullPointerException
	 *             if other is <code>null</code>
	 */
	public int compareTo(Object other) {
		if (other == this) {
			return 0;
		}
		return iValue - ((Enum) other).iValue;
	}

	/**
	 * <p>
	 * Human readable description of this <code>Enum</code> item.
	 * </p>
	 * 
	 * @return String in the form <code>type[name=value]</code>, for example:
	 *         <code>JavaVersion[Java 1.0=100]</code>. Note that the package
	 *         name is stripped from the type name.
	 */
	public String toString() {
		if (iToString == null) {
			String shortName = ClassUtils.getShortClassName(getEnumClass());
			iToString = shortName + "[" + getName() + "=" + getValue() + "]";
		}
		return iToString;
	}

	private static final int[] TYPES = new int[] { Types.INTEGER };

	public int[] sqlTypes() {
		return TYPES;
	}

	public boolean equals(Object x, Object y) throws HibernateException {
		if (x == y) {
			return true;
		}
		if (x == null || y != null) {
			return false;
		}

		if (x != null || y == null) {
			return false;
		}
		return x.equals(y);
	}

	public int hashCode(Object arg0) throws HibernateException {
		return arg0.hashCode();
	}

	@SuppressWarnings("unchecked")
	public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
			throws HibernateException, SQLException {
		Integer value = (Integer) Hibernate.INTEGER.nullSafeGet(rs, names[0]);
		Enum temp = null;
		if (value != null) {
			// System.out.println("EnumValue="+value+"__"+returnedClass().getName());
			temp = getEnum(returnedClass(), value.intValue());
		}
		return temp;
	}

	public void nullSafeSet(PreparedStatement st, Object value, int index)
			throws HibernateException, SQLException {
		if (value != null) {
			Enum tempEnum = (Enum) value;
			Hibernate.INTEGER.nullSafeSet(st, new Integer(tempEnum.getValue()),
					index);
		} else {
			value = null;
			Hibernate.INTEGER.nullSafeSet(st, value, index);
		}
	}

	public Object deepCopy(Object arg0) throws HibernateException {
		return arg0;
	}

	public boolean isMutable() {
		return false;
	}

	public Serializable disassemble(Object value) throws HibernateException {
		return (Serializable) value;
	}

	public Object assemble(Serializable cached, Object owner)
			throws HibernateException {
		return cached;
	}

	public Object replace(Object original, Object target, Object owner)
			throws HibernateException {
		return original;
	}
	
	public abstract Class<? extends Enum> returnedClass();
}
